#!/usr/bin/python2.4

# C6304: copyright missing
# pylint: disable-msg=C6304

# W0212: Access to protected member (sleazy)
# pylint: disable-msg=W0212

"""Manages optimal boat performance from polar diagrams.

This maps TWS and TWA to target boat speed.
"""

from matrix import Polar
from matrix import Ratio


class SailPerformanceItem(object):
  """Holds one row from the performance table."""

  def __init__(self,
               true_wind_speed,
               point_of_sail,
               true_wind_angle,
               target_boat_speed,
               apparent_wind_speed,
               apparent_wind_angle):

    self._point_of_sail = point_of_sail
    self._target_boat_speed = target_boat_speed

    self._true_wind = Polar(true_wind_speed, true_wind_angle)
    self._apparent_wind = Polar(apparent_wind_speed, apparent_wind_angle)


class OptimalSailingComputation(object):
  """Models the optimum boat speed function.

  In this file, true_wind.angle is relative to the bow of the boat,
    eg 0.0 is dead into the wind.

  In other contexts, true_wind.angle is relative to the bottom.
  """

  def __init__(self):
    self._entrys = {}
    self._LoadPerformanceItems()
    self._sorted_windspeeds = list(self._entrys)
    self._sorted_windspeeds.sort()
    self._nindexes = len(self._sorted_windspeeds)

  def _Add(self, a_performance_item):
    self._entrys.setdefault(a_performance_item._true_wind.Mag(), [])
    self._entrys[a_performance_item._true_wind.Mag()].append(a_performance_item)

  def Index(self, tw, trace=False):
    """Returns the target boat speed.

    Args:
      tw: The true wind speed/angle (a Polar).
      trace: If True, print to stdout some tracing information.

    Returns:
      The target boat speed.
    """
    true_wind_speed = abs(tw.Mag())
    i1 = None
    i2 = None
    for i in xrange(self._nindexes -1):
      if (self._sorted_windspeeds[i] <= true_wind_speed and
          true_wind_speed < self._sorted_windspeeds[i + 1]):
        i1 = i + 0
        i2 = i + 1
        break

    ws1 = self._sorted_windspeeds[i1]
    ws2 = self._sorted_windspeeds[i2]
    if trace:
      print 'i1=%d i2=%d ws1=%g ws2=%g' % (i1, i2, ws1, ws2)
    i1_speed = self._IndexByTrueWindAngle(tw.Angle(), self._entrys[ws1])
    i2_speed = self._IndexByTrueWindAngle(tw.Angle(), self._entrys[ws2])

    speed = Ratio(ws1, true_wind_speed, ws2, i1_speed, i2_speed)
    return speed

  def _IndexByTrueWindAngle(self, twa, entry_list):
    """Returns the target boat speed given a true wind angle.

    Args:
      twa: given true wind angle
      entry_list: a list of SailPerformanceItem, sorted by the true wind angle.

    Returns:
      The target boat speed, interpolated.
    """
    if twa < entry_list[0]._true_wind.Angle():
      # Pinching or in irons
      return 0.0

    if twa > entry_list[len(entry_list) - 1]._true_wind.Angle():
      # Dead down wind; don't do that!
      return 0.0

    last_entry = entry_list[0]
    for entry in entry_list[1:]:
      if (last_entry._true_wind.Angle() <= twa and
          twa <= entry._true_wind.Angle()):
        return Ratio(
            last_entry._true_wind.Angle(), twa, entry._true_wind.Angle(),
            last_entry._target_boat_speed, entry._target_boat_speed)
      last_entry = entry
    return 0.0


class J42OptimalSailingComputation(OptimalSailingComputation):
  """Manages optimal sailing performance data for a J42."""

  def _LoadPerformanceItems(self):
    # "True Wind Speed","Point of Sail", ...
    #    ... "True wind angle","Target Boat Speed", ...
    #    ... "Apparent Wind Speed","Apparent Wind Angle"

    cons = SailPerformanceItem

    # Lower bound sentinal; using sentinals makes indexing and boundaries
    # easier to do.
    self._Add(cons(0, 'Becalmed', 0, 0, 0, 0))
    self._Add(cons(0, 'Becalmed 60', 0, 0, 0, 0))
    self._Add(cons(0, 'Becalmed 90', 0, 0, 0, 0))
    self._Add(cons(0, 'Becalmed 120', 0, 0, 0, 0))
    self._Add(cons(0, 'Becalmed-Spinnaker', 0, 0, 0, 0))
    self._Add(cons(0, 'Becalmed-Jib', 0, 0, 0, 0))
    self._Add(cons(0, 'Becalmed-Jib with pole', 0, 0, 0, 0))

    self._Add(cons(6, 'Optimum Beat', 47, 4.88, 9.8, 26))
    self._Add(cons(6, '60', 60, 5.75, 10.0, 30))
    self._Add(cons(6, '90', 90, 6.13, 8.5, 44))
    self._Add(cons(6, '120', 120, 4.45, 5.3, 73))
    self._Add(cons(6, 'Optimum Run-Spinnaker', 141, 4.52, 3.8, 92))
    self._Add(cons(6, 'Optimum Run-Jib', 169, 2.89, 3.1, 158))
    self._Add(cons(6, 'Optimum Run-Jib with pole', 173, 3.13, 2.8, 164))

    self._Add(cons(8, 'Optimum Beat', 46, 5.88, 12.6, 26))
    self._Add(cons(8, '60', 60, 6.84, 12.6, 32))
    self._Add(cons(8, '90', 90, 7.09, 10.5, 47))
    self._Add(cons(8, '120', 120, 5.54, 6.9, 76))
    self._Add(cons(8, 'Optimum Run-Spinnaker', 144, 5.58, 4.8, 100))
    self._Add(cons(8, 'Optimum Run-Jib', 169, 3.86, 4.1, 158))
    self._Add(cons(8, 'Optimum Run-Jib with pole', 173, 4.16, 3.7, 164))

    self._Add(cons(10, 'Optimum Beat', 44, 6.44, 14.9, 26))
    self._Add(cons(10, '60', 60, 7.33, 14.7, 34))
    self._Add(cons(10, '90', 90, 7.57, 12.3, 52))
    self._Add(cons(10, '120', 120, 6.46, 8.6, 79))
    self._Add(cons(10, 'Optimum Run-Spinnaker', 147, 6.39, 5.8, 110))
    self._Add(cons(10, 'Optimum Run-Jib', 169, 4.75, 5.1, 160))
    self._Add(cons(10, 'Optimum Run-Jib with pole', 173, 5.11, 4.7, 164))

    self._Add(cons(12, 'Optimum Beat', 42, 6.68, 17.1, 26))
    self._Add(cons(12, '60', 60, 7.63, 16.5, 36))
    self._Add(cons(12, '90', 90, 7.92, 14.0, 56))
    self._Add(cons(12, '120', 120, 7.03, 10.2, 83))
    self._Add(cons(12, 'Optimum Run-Spinnaker', 161, 6.52, 6.2, 141))
    self._Add(cons(12, 'Optimum Run-Jib', 170, 5.59, 6.3, 160))
    self._Add(cons(12, 'Optimum Run-Jib with pole', 173, 6.01, 5.8, 165))

    self._Add(cons(14, 'Optimum Beat', 41, 6.82, 19.1, 26))
    self._Add(cons(14, '60', 60, 7.82, 18.3, 37))
    self._Add(cons(14, '90', 90, 8.21, 15.7, 58))
    self._Add(cons(14, '120', 120, 7.39, 11.8, 87))
    self._Add(cons(14, 'Optimum Run-Spinnaker', 168, 6.99, 7.2, 157))
    self._Add(cons(14, 'Optimum Run-Jib', 170, 6.37, 7.4, 162))
    self._Add(cons(14, 'Optimum Run-Jib with pole', 173, 6.80, 6.9, 166))

    self._Add(cons(16, 'Optimum Beat', 40, 6.92, 21.1, 27))
    self._Add(cons(16, '60', 60, 7.96, 20.0, 39))
    self._Add(cons(16, '90', 90, 8.47, 17.4, 61))
    self._Add(cons(16, '120', 120, 7.70, 13.4, 90))
    self._Add(cons(16, 'Optimum Run-Spinnaker', 172, 7.42, 8.7, 166))
    self._Add(cons(16, 'Optimum Run-Jib', 172, 6.96, 8.7, 166))
    self._Add(cons(16, 'Optimum Run-Jib with pole', 173, 7.34, 8.3, 168))

    self._Add(cons(20, 'Optimum Beat', 39, 7.03, 24.9, 27))
    self._Add(cons(20, '60', 60, 8.13, 23.5, 41))
    self._Add(cons(20, '90', 90, 8.90, 20.4, 64))
    self._Add(cons(20, '120', 120, 8.23, 16.8, 95))
    self._Add(cons(20, 'Optimum Run-Spinnaker', 174, 8.18, 11.8, 170))
    self._Add(cons(20, 'Optimum Run-Jib', 174, 7.73, 11.8, 170))
    self._Add(cons(20, 'Optimum Run-Jib with pole', 174, 8.13, 11.4, 169))

    # Sentinel value same as at 20 kts of wind; above 20kts, the boat
    # will do no better.
    self._Add(cons(100, 'Sentinel Optimum Beat', 39, 7.03, 24.9, 27))
    self._Add(cons(100, '60', 60, 8.13, 23.5, 41))
    self._Add(cons(100, '90', 90, 8.90, 20.4, 64))
    self._Add(cons(100, '120', 120, 8.23, 16.8, 95))
    self._Add(cons(100, 'Optimum Run-Spinnaker', 174, 8.18, 11.8, 170))
    self._Add(cons(100, 'Optimum Run-Jib', 174, 7.73, 11.8, 170))
    self._Add(cons(100, 'Optimum Run-Jib with pole', 174, 8.13, 11.4, 169))
